using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WindowsPhoneGame1.Shapes;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input.Touch;
using WindowsPhoneGame1.Collisions;

namespace WindowsPhoneGame1.LevelItems
{
    public class Car : LevelItem
    {
        private string name;
        private Wheel frontWheel;
        private Wheel rearWheel;

        private Vector2 velocity = Vector2.Zero;
        private int gear = 0;
        private float acceleration = 18000;
        
        private float minSpeed;
        private float critSpeed;
        private float maxSpeed;
        private float rotationsPerSecond; // rotations of the gear box
        private const float maxRots = 9000;

        private float angularVelocity = 45; // angular velocity of the car
        private float rotation = 0; // rotation of the car

        private Vector2 rearWheelOffset, frontWheelOffset;

        public Car(string name, PointF position, Texture2D chassisTexture, Texture2D wheelTexture, Vector2 frontWheelOffset,
            Vector2 rearWheelOffset)
            : base(position,
                new Polygon(
                    new PointF[]
                    {
                        new PointF(0, 0),
                        new PointF(chassisTexture.Width, 0),
                        new PointF(chassisTexture.Width, chassisTexture.Height),
                        new PointF(0, chassisTexture.Height)
                    }
                    ), chassisTexture)
        {
            this.name = name;
            frontWheel = new Wheel(new PointF(frontWheelOffset.X, frontWheelOffset.Y), wheelTexture, new Circle(position, wheelTexture.Height / 2f), this);
            rearWheel = new Wheel(new PointF(rearWheelOffset.X, rearWheelOffset.Y), wheelTexture, new Circle(position, wheelTexture.Height / 2f), this);
            Position = new PointF(position.X - chassisTexture.Width, position.Y);
            this.rearWheelOffset = rearWheelOffset;
            this.frontWheelOffset = frontWheelOffset;
        }

        public void Update(GameTime gameTime)
        {
            rearWheel.Update(gameTime, this.Position, 10);
            frontWheel.Update(gameTime, this.Position, 10);

            acceleration = GetAccelleration(velocity.Length(), gear);
            SetSpeedLimits(velocity.Length(), gear);
            rotationsPerSecond = GetRotsPerSecond();

            Rotate(angularVelocity * (float)gameTime.ElapsedGameTime.TotalSeconds);

            velocity += new Vector2(acceleration * (float)gameTime.ElapsedGameTime.TotalHours, 0);
            Position = new PointF(Position.X + (velocity.X < 0 ? 0 : velocity.X), Position.Y + velocity.Y);
        }

        public void Rotate(float degrees)
        {
            rotation += MathHelper.ToRadians(degrees);
            PointF p = new PointF(frontWheelOffset.X - rearWheelOffset.X, frontWheelOffset.Y - rearWheelOffset.Y);

            PointF q = new PointF(
                p.X * (float)Math.Cos(rotation),
                p.X * (float)Math.Sin(rotation));

            p = new PointF(
                q.X + rearWheel.Position.X,
                q.Y + rearWheel.Position.Y);
            frontWheel.Position = p;
        }

        public void ShiftGearUp()
        {
            gear = gear == 5 ? 5 : gear + 1;
        }

        public void Break()
        {
            gear = 0;
        }

        // Gaussian-like formula to calculate accelleration.
        private float GetAccelleration(float speed, int gear)
        {
            int n = gear;

            if (gear == 0 && velocity.X > 0)
                return -180000;
            
            if(gear == 0)
            {
                velocity.X = 0;
                return 0;
            }

            float s = speed;
            float sigma = 2 + 0.2f * (n - 1);
            int mu = 0;
            int top = 6 - n;

            if (n > 0)
            {
                for (int i = 1; i <= n; i++)
                    mu += i;
                mu--;
            }

            float minSpeed = mu - 2.33f * sigma;
            float maxSpeed = mu + 2.33f * sigma;

            float accelleration;
            //if (speed < minSpeed || speed > maxSpeed)
            //    accelleration = 0;
            //else
            accelleration = (float)Math.Exp(-0.5 * ((0.1 * s - mu) / sigma) * ((0.1 * s - mu) / sigma)) * top / 5 * 18000;

            return accelleration;
        }

        private void SetSpeedLimits(float speed, int gear)
        {
            int n = gear;

            float s = speed;
            float sigma = 2 + 0.2f * (n - 1);
            int mu = 0;

            if (n > 0)
            {
                for (int i = 1; i <= n; i++)
                    mu += i;
                mu--;
            }

            minSpeed = (mu - 2.33f * sigma) * 10;
            critSpeed = (mu + 1.65f * sigma) * 10;
            maxSpeed = (mu + 2.33f * sigma) * 10;
        }

        // Call this only after SetSpeedLimits(float, int) has been called!
        private float GetRotsPerSecond()
        {
            float range = maxSpeed - minSpeed;

            float slope = maxRots / (range);

            float rots = (velocity.Length() - minSpeed) * slope;
            if (rots < 0)
                rots = 0;

            return rots;
        }

        public override void Draw(GameTime gameTime, PointF position, Game1 game)
        {
            PointF p = new PointF(0, 0);
            rearWheel.Draw(gameTime, position, game);
            frontWheel.Draw(gameTime, position, game);

            Vector2 origin = new Vector2(
                    rearWheel.Position.X + rearWheel.CollisionShape.Bounds.Width / 2f,
                    rearWheel.Position.Y + rearWheel.CollisionShape.Bounds.Height / 2f);

            game.SpriteBatch.Draw(
                texture,
                new Vector2(
                    Position.X - position.X,
                    game.GraphicsDevice.Viewport.Height - CollisionShape.Bounds.Height - 3)
                    + VIEW_OFFSET + origin,
                texture.Bounds,
                Color.White, rotation, 
                origin,
                1,
                SpriteEffects.None,
                0);
        }

        public float GetSpeed()
        {
            return velocity.Length();
        }

        public Wheel GetFrontWheel()
        {
            Wheel wheel = new Wheel(new PointF(Position.X + frontWheel.Position.X, Position.Y + frontWheel.Position.Y),
                frontWheel.Texture, frontWheel.CollisionShape, this);

            return wheel;
        }

        public Wheel GetRearWheel()
        {
            Wheel wheel = new Wheel(new PointF(Position.X + rearWheel.Position.X, Position.Y + rearWheel.Position.Y),
                rearWheel.Texture, rearWheel.CollisionShape, this);

            return wheel;
        }
    }
}
